#!/usr/bin/env python
# -*- mode: python; coding: utf-8; -*-
##---------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Franz Xaver Johannes Oberhumer
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh
## Copyright (C) 20010 Jeremy Austin-Bardo
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##---------------------------------------------------------------------------##

import random

from ... import autostacks
from ... import pilelib as stack
from ...formlib import Layout
from ...gamelib import state, game
from ...hint import AbstractHint
from ...packlib import info as cs

Wrap = stack.StackWrapper

class _SlideStack(stack.ReserveStack):
    """
    Stack.ReserveStack implementing a basic slide style stack to allow contents
    of stack to move to an open neighbor
    """

    def canFlipCard(self):
        return False

    def canDropCards(self, stacks):
        return (None, 0)

    def cancelDrag(self, event=None):
        if event is None:
            self._stopDrag()

    def _locate(self, event):
        """
        we need to override this because the shade may be hiding
        the tile (from Tk's stacking view)
        """
        return len(self.cards) - 1

    def initBindings(self):
        self.group.bind("<1>", self._Stack__clickEventHandler)
        self.group.bind("<Control-1>", self._Stack__controlclickEventHandler)

    def blockMap(self):
        id_, rlen = self.id, max(self.game.ROWS)
        line, column = id_/rlen, [], []
        row = [r.id for r in self.game.s['rows'][line*sqrt:sqrt+line*sqrt]]
        while id_ >= rlen:
            id_ -= rlen
        while id_ < sum(self.game.ROWS):
            column.append(id_)
            id_ += rlen
        return [row, column]

    def basicIsBlocked(self):
        stack_map = self.blockMap()
        for j in range(2):
            for i in range(len(stack_map[j])):
                if not self.game.s['rows'][stack_map[j][i]].cards:
                    return False
        return True

    def clickHandler(self, event):
        if (not self.cards or self.game.drag['stack'] is self or
            self.basicIsBlocked()):
            return True
        self.game.playSample("move", priority=10)
        stack_map = self.blockMap()
        for j in range(2):
            dir = 1
            for i in range(len(stack_map[j])):
                to_stack = self.game.s['rows'][stack_map[j][i]]
                if to_stack is self:
                    dir = -1
                if not to_stack.cards:
                    self._stopDrag()
                    step = 1
                    from_stack = self.game.s['rows'][stack_map[j][i + dir]]
                    while not from_stack is self:
                        self.move(1, from_stack, to_stack, frames=0, sound=False)
                        to_stack = from_stack
                        step += 1
                        from_stack = self.game.s['rows'][stack_map[j][i+dir*step]]
                    self.move(1, self, to_stack, frames=0, sound=False)
                    return True
        return True


class MatrixGame(game.Game):
    """
    Basic slide-stack style abstract game. Creates a box with one empty stack
    allow cards to slide and arrange in rank order.
    """
    _talon_ = stack.InitialDealTalon
    _rows_ = _SlideStack
    ROWS = 0

    def _create_game(self):
        l, s = Layout(self), self.s

        # Set window size
        self.setSize(2 * l.XM + l.XS * max(self.ROWS),
                     2 * l.YM + len(self.ROWS) * l.YS)

        # Create rows
        y = l.YM
        for j in self.ROWS:
            x = l.XM
            for i in range(j):
                s['rows'].append(self._rows_(x, y, game=self))
                x += l.XS
            y += l.YS
        x, y = -2 * l.XS, -2 * l.YS
        s['talon'] = self._talon_(x, y, game=self)
        l.defaultStackGroups()

    def _pack_place(self, cards):
        """
        _pack_place(cards:seq(Card ...)) -> seq(Card ...)

        Overide to return a solveable card sort.
        """
        ncards = len(cards) - 1
        for c in cards:
            if c['rank'] == ncards:
                cards.remove(c)
                break
        n = 0
        for i in range(ncards - 1):
            for j in range(i + 1, ncards):
                if cards[i]['rank'] > cards[j]['rank']:
                    n += 1
        cards.reverse()
        if n % 2:
            cards[0], cards[1] = cards[1], cards[0]
        return [c] + cards

    def _start(self, **kw):
        """
        Deal initial cards for new game.
          - s[rows] 1 ea.

        .. \_start([rows=4]) -> None
        """
        self.s['talon'].dealRow(rows=self.s['rows'][:self['ncards']], frames=3)

    @property
    def is_complete(self):
        if self.busy:
            return False
        l = len(self.s['rows']) - 1
        for row in self.s['rows'][:l]:
            if not row.cards or not row.BASECARD['rank'] == row.id:
                return False
        self.s['talon'].dealRow(rows=self.s['row'][l:], frames=0)
        return True


class _PeggedHint(AbstractHint):
    """
    AbstractHint implement understanding of jump style to stack hint.

    FIXME: no intelligence whatsoever is implemented here
    """

    def compute_hints(self):
        """
        compute_hints() -> None

        compute hints - main hint intelligence
        """
        rows = self._stacks['set']['rows']
        for t in (r for r in rows if not r.cards):
            for dx, dy in rows[0].game.STEPS:  # FIXME kludge
                r = rows[0].game.map.get((t.pos[0] + dx, t.pos[1] + dy))
                if not r or not r.cards or not t.acceptsCards(r, r.cards):
                    continue
                # braindead scoring...
                score = 10000 + random.randint(0, 9999)
                self.add_hint(score, 1, r, t)


class _JumpStack(stack.ReserveStack):
    """
    Stack.ReserveStack implementing a basic jump style stack to allow contents
    of stack to jump over a neighbor removing it in the process.
    """

    def cardsBuild(self, from_stack, cards):
        if not super(_JumpStack, self).cardsBuild(self, from_stack, cards):
            return False
        return self._getMiddleStack(from_stack) is not None

    def canDropCards(self, stacks):
        return (None, 0)

    @state.move_state('fill')
    def move_pile(self, ncards, to_stack, **kwargs):
        other_stack = to_stack._getMiddleStack(self)
        self.game.do_move_pile(ncards, to_stack, frames=0)  # low-level
        self.game.playSample("drop", priority=200)
        self.game.do_move_pile(ncards, other_stack,  # low-level
                               self.game.s['foundations'][0], **kwargs)
        self.fill_stack()
        other_stack.fill_stack()

    def _getMiddleStack(self, from_stack):
        dx, dy = from_stack.pos[0]-self.pos[0], from_stack.pos[1]-self.pos[1]
        #print dx, dy
        if not self.game.STEP_MAP.get((dx, dy)):
            return None
        s = self.game.map.get((self.pos[0] + dx/2, self.pos[1] + dy/2))
        if not s or not s.cards:
            return None
        return s

    def copyModel(self, clone):
        super(_JumpStack, self).copyModel(clone)
        clone.pos = self.pos


class PeggedGame(game.Game):
    """
    Basic jump-stack style abstract game. Creates a box with one empty stack
    allow cards to jump over and remove the card.
    """
    _hint_ = _PeggedHint
    _talon_ = stack.InitialDealTalon
    _row_ = _JumpStack
    _foundation_ = Wrap(stack.AbstractFoundation, base_rank=cs.ANY,
                        max_move=0, max_accept=0)
    EMPTY_STACK_ID = None
    ROWS = 0

    def _create_game(self):
        l, s = Layout(self), self.s
        m = max(self.ROWS)
        self.setSize(l.XM + m * l.XS, l.YM + len(self.ROWS) * l.YS)
        self.map = {}
        y = l.YM
        for i, r in enumerate(self.ROWS):
            d = (m - r)
            x = l.XM+d*l.XS/2
            for j in range(r):
                s['rows'].append(self._row_(x, y, game=self))
                pos = s['rows'][-1].pos = (d + 2 * j, 2 * i)
                self.map[pos] = s['rows'][-1]
                x += l.XS
            y += l.YS
        x, y = self.width - l.XS, l.YM
        s['foundations'].append(self._foundation_(x, y, game=self,
                                                  max_cards=self['ncards']))
        l.createText(s['foundations'][0], "s")
        y = self.height - l.YS
        s['talon'] = self._talon_(x, y, game=self)
        s['internals'].append(stack.InvisibleStack(self))
        self.STEP_MAP = {}
        for step in self.STEPS:
            self.STEP_MAP[step] = 1
        if self.EMPTY_STACK_ID is None:
            self.EMPTY_STACK_ID = len(s.rows) / 2
        l.defaultStackGroups()

    def _pack_shuffle(self, cards, parts=1):
        """
        _pack_shuffle(cards:seq(Card ...)[, parts:int=1]) -> seq(Card ...)

        Overide to returns given cards reversed. May overide to modify method.
        """
        cards.reverse()
        return cards

    def _start(self, **kw):
        """
        Deal initial cards for new game.
          - s[rows] 1 ea up.

        .. \_start([rows=4]) -> None
        """
        ncards = len(self.cards) - len(self.s['rows'])
        if ncards > 0:
            self.s['talon'].move_pile(ncards, self.s['internals'][0], frames=0)
        rows = list(self.s['rows'])
        rows.remove(rows[self.EMPTY_STACK_ID])
        self.s['talon'].dealRow(rows=rows, frames=4)

    @property
    def is_complete(self):
        return sum(len(s.cards)
                   for s in self.s['foundations']) + 1 == self['si']['ncards']

    def _autopiles(self, event=None):
        """
        Return piles for auto or quick move.

        Overide to disable autoflip, autodrop, quickplay.

        .. \_autopiles(self, event=None) -> seq(seq(Pile ...), seq(Pile ...),
             seq(Pile ...))
        """
        return autostacks.none()

    @property
    def is_perfect(self):
        """
        Return game perfect.

        Augment to check for correct empty pile.

        .. is_success -> int
        """
        perfect = super(PeggedGame, self).is_perfect
        stacks = [r for r in self.s['rows'] if r.cards]
        assert len(stacks) == 1
        return perfect and stacks[0].id == self.EMPTY_STACK_ID


    @property
    def _highlight_piles(self):
        """
        Get stacks to highlight for possible move.

        :return: `s.rows` with neighbor's empty.
        :rtype: tuple
        """
        return ()
        rows = []
        for r in self.s['rows']:
            if not r.cards:
                continue
            rx, ry = r.pos
            for dx, dy in self.STEPS:
                s = self.map.get((rx + dx, ry + dy))
                if s and not s.cards:
                    m = self.map.get((rx + dx/2, ry + dy/2))
                    if m and m.cards:
                        rows.append(r)
        return ((rows, 1), )